home *** CD-ROM | disk | FTP | other *** search
/ Magnum One / Magnum One (Mid-American Digital) (Disc Manufacturing).iso / d3 / ddjhptxt.arc / WEEKS.LST < prev   
File List  |  1990-06-05  |  54KB  |  1,629 lines

  1. LISTING ONE
  2.  
  3. /* FILETEST.HPP Written by Kevin D. Weeks Released to the Public Domain  */
  4.  
  5. #ifndef FILESPEC_HPP                        // prevent multiple #includes
  6. #define FILESPEC_HPP
  7.  
  8. #include <stdio.h>
  9.  
  10. #define ERR     -1
  11. // create a boolean type
  12. typedef enum{FALSE,TRUE}     bool;
  13.  
  14. // specify attribute sizes
  15. #define SIZE_DEVICE 2
  16. #define SIZE_PREFIX 64
  17. #define SIZE_NAME   9                       // the dot is part of the name
  18. #define SIZE_SUFFIX 3
  19.  
  20. // these constants are used as flags in the condition attribute
  21. #define FLAG_DEVICE     0x0001
  22. #define FLAG_PREFIX     0x0002
  23. #define FLAG_NAME       0x0004
  24. #define FLAG_SUFFIX     0x0008
  25. #define INCOMPLETE      0x000f
  26. #define INVALID_CHAR    0x00f0
  27. #define READ_ONLY       0x0100
  28.  
  29. class   File_Spec
  30. {
  31.     // first define the attributes
  32.     char    device[SIZE_DEVICE + 1];        // under MS-DOS, the disk drive
  33.     char    *prefix;                        //   "     "   , the path
  34.     char    name[SIZE_NAME + 1];            //   "     "   , still the name
  35.     char    suffix[SIZE_SUFFIX + 1];        //   "     "   , the extension
  36.     char    *request;                       // pointer to response string for
  37.                                             // get_XXXX() methods
  38.     int     prefix_length;
  39.     int     request_length;
  40.     unsigned int    condition;              // current status of the object
  41.  
  42.     // and then the private methods
  43.     bool    check_prefix(void);             // determine completeness of prefix
  44.     bool    parse_prefix(void);             // interpret relative prefix
  45.     bool    check_chars(char *string, unsigned int attrib_flag);
  46.                                             // test for valid MS-DOS file chars
  47.     void    copy(const File_Spec& original);  // copy another file spec
  48.     bool    clear_attribute(char *attribute, unsigned int attrib_flag);
  49.     bool    realloc(char **pointer, int *length, int new_length);
  50.  
  51.     // make class File_Spec a friend of itself
  52.     friend  class File_Spec;
  53.  
  54.   // now the public methods
  55.   public:
  56.     // construct file specifications
  57.             File_Spec(void);
  58.             File_Spec(const char *file);
  59.             File_Spec(const File_Spec& original);
  60.  
  61.     // destroy a file specification
  62.             .MDSD/w.MDNM/File_Spec(void);
  63.  
  64.     // return status of object
  65.     unsigned int    status(void);
  66.  
  67.     // extend the language by overloading the = operator
  68.     File_Spec   operator=(const File_Spec& original);
  69.  
  70.     // ALL get_XXXX() methods guarantee to return NUL-terminated strings
  71.     char    *get_device(void);
  72.     char    *get_prefix(void);
  73.     char    *get_name(void);
  74.     char    *get_suffix(void);
  75.     char    *filespec(void);
  76.  
  77.     // ALL change_XXXX() methods guarantee to copy no more than SIZE_n
  78.     // characters from the pass parameter
  79.     bool    change_device(const char *string = NULL);
  80.     bool    change_prefix(const char *string = NULL);
  81.     bool    change_name(const char *string = NULL);
  82.     bool    change_suffix(const char *string = NULL);
  83.  
  84.     // disables/enables change_XXXX()
  85.     void    read_only(bool flag);
  86.  
  87.     // attempt to complete the file specification
  88.     bool    complete(void);
  89. };
  90.  
  91. #endif
  92.  
  93.  
  94. LISTING TWO
  95.  
  96. /* FILESPEC.CPP Written by Kevin D. Weeks Released to the Public Domain  */
  97.  
  98. #include <stdio.h>
  99. #include <errno.h>
  100. #include <string.h>
  101. #include <ctype.h>
  102. #include "filespec.hpp"
  103.  
  104. // this declaration instructs the compiler to NOT perform name-mangling
  105. // on these functions.
  106. extern "C"
  107. {
  108.     extern char     ll_get_drive(void);
  109.     extern int      ll_get_cwd(int, char *);
  110.     extern unsigned int ll_write(int, unsigned int, const void *);
  111. }
  112.  
  113. // these constants are states used in parsing the file string
  114. #define DEVICE  1
  115. #define PREFIX  2
  116. #define NAME    3
  117. #define SUFFIX  4
  118.  
  119. // a macro to return out of memory errors
  120. #define MEM_ERR(length) { errno = ENOMEM; length = 0; return NULL; }
  121.  
  122. extern volatile int errno;
  123.  
  124. /*  This final File_Spec constructor is passed a character string that it
  125.     attempts to parse into its various components. Parsing is done with
  126.     a finite state machine that begins at the end of the string and backs
  127.     up to the beginning, changing state as it encounters the element delimiters
  128.     ':', '.', and '\'. Once the string has been parsed the components are
  129.     checked for completeness and for the validity of the file characters.
  130.     In order to correctly interpret a string with a prefix but no name the
  131.     string must end with a '\'. Also note that any individual component
  132.     device, name, etc.) that is too long is truncated to a legal length.
  133. */
  134. File_Spec::File_Spec(const char *file)
  135. {
  136.     char    *tmp_file;                  // local copy of tmp_file
  137.     char    *tmp_prefix;                // temporary string for the prefix
  138.     int     pos;                        // current position in tmp_file string
  139.     int     state;                      // current state
  140.     int     i;                          // trash variable
  141.     // initialize everything that needs initializing
  142.     prefix = NULL;
  143.     prefix_length = 0;
  144.     request_length = 0;
  145.     condition = 0;
  146.     device[0] = device[SIZE_DEVICE] = '\0';
  147.     name[0] = name[SIZE_NAME] = '\0';
  148.     suffix[0] = suffix[SIZE_SUFFIX] = '\0';
  149.     errno = 0;
  150.     if (file == NULL || *file == '\0')
  151.     {
  152.         condition = INCOMPLETE;
  153.         return;
  154.     }
  155.     if ((tmp_file = new char[strlen(file) + 1]) == NULL)
  156.     {
  157.         errno = ENOMEM;
  158.         return;
  159.     }
  160.     strcpy(tmp_file,file);
  161.     if ((tmp_prefix = new char[SIZE_PREFIX + 1]) == NULL)
  162.     {
  163.         errno = ENOMEM;
  164.         return;
  165.     }
  166.     tmp_prefix[0] = tmp_prefix[SIZE_PREFIX] = '\0';
  167.     pos = strlen(tmp_file) - 1;             // set pos to last character
  168.     // this while loop is the finite state machine mentioned above. note
  169.     // that the strncpy() calls copy everthing from just beyond the
  170.     // character that satisfies the case, and then the tmp_file is truncated
  171.     // at that point with a '\0'.
  172.     state = SUFFIX;
  173.     do
  174.     {
  175.         switch (tmp_file[pos])
  176.         {
  177.             case '.':
  178.                 // a dot only counts in the SUFFIX state
  179.                 if (state == SUFFIX)
  180.                 {
  181.                     strncpy(suffix,&tmp_file[pos + 1],SIZE_SUFFIX);
  182.                     tmp_file[pos + 1] = '\0';
  183.                     state = NAME;
  184.                 }
  185.                 else
  186.                     if (state == NAME)
  187.                         // this means we've got two or more dots in a name
  188.                         // which is illegal. flag it as an invalid char
  189.                         condition |= FLAG_NAME << 4;
  190.                 break;
  191.             case '\\':
  192.                 if ((state == SUFFIX) || (state == NAME))
  193.                 {
  194.                     strncpy(name,&tmp_file[pos + 1],SIZE_NAME);
  195.                     tmp_file[pos + 1] = '\0';
  196.                     state = PREFIX;
  197.                 }
  198.                 break;
  199.             case ':':
  200.                 if ((state == SUFFIX) || (state == NAME))
  201.                 {
  202.                     strncpy(name,&tmp_file[pos + 1],SIZE_NAME);
  203.                     tmp_file[pos + 1] = '\0';
  204.                     state = DEVICE;
  205.                 }
  206.                 else
  207.                     if (state == PREFIX)
  208.                     {
  209.                         strncpy(tmp_prefix,&tmp_file[pos + 1],SIZE_PREFIX);
  210.                         tmp_file[pos + 1] = '\0';
  211.                         state = DEVICE;
  212.                     }
  213.                 break;
  214.         }
  215.         --pos;                              // go to next character
  216.     } while(pos >= 0);
  217.     // now resolve whatever state we ended up in
  218.     if ((state == SUFFIX) || (state == NAME))
  219.         strncpy(name,tmp_file,SIZE_NAME);
  220.     else
  221.         if (state == PREFIX)
  222.             strncpy(tmp_prefix,tmp_file,SIZE_PREFIX);
  223.         else
  224.             strncpy(device,tmp_file,SIZE_DEVICE);
  225.     // validate the device
  226.     device[1] = ':';
  227.     device[2] = '\0';
  228.     if (device[0] == '\0')
  229.         condition |= FLAG_DEVICE;
  230.     else
  231.     {
  232.         // make the device upper-case for simplicity's sake later on
  233.         device[0] = toupper(device[0]);
  234.         if (device[0] < 'A' || device[0] > 'Z')
  235.             condition |= FLAG_DEVICE << 4;
  236.     }
  237.     // use the existing change_prefix() method to create the prefix and
  238.     // validate it
  239.     change_prefix(tmp_prefix);
  240.     delete[SIZE_PREFIX + 1] tmp_prefix;
  241.     // now validate the name
  242.     if (name[0] == '\0')
  243.         condition |= FLAG_NAME;
  244.     else
  245.     {
  246.         if (name[0] == '.')
  247.         {
  248.             condition |= FLAG_NAME;
  249.             name[0] = '\0';
  250.         }
  251.         else
  252.             if (check_chars(name,FLAG_NAME))
  253.             {
  254.                 // as far as we're concerned name HAS to end with a dot.
  255.                 i = strlen(name);
  256.                 if (name[i - 1] != '.')
  257.                 {
  258.                     if (i == SIZE_NAME)
  259.                         i = SIZE_NAME - 1;
  260.                     name[i++] = '.';
  261.                     name[i] = '\0';
  262.                 }
  263.             }
  264.     }
  265.     // and suffix
  266.     if (suffix[0] != '\0')
  267.         check_chars(suffix,FLAG_SUFFIX);
  268.  
  269. }
  270. /*  This constructor creates an empty object suitable for later filling. */
  271. File_Spec::File_Spec(void)
  272. {
  273.     // Set both ends of device, name, and suffix to NUL. Since strncpy()
  274.     // is used later on this guarantees these three are always NUL-terminated.
  275.     device[0] = device[SIZE_DEVICE] = '\0';
  276.     name[0] = name[SIZE_NAME] = '\0';
  277.     suffix[0] = suffix[SIZE_SUFFIX] = '\0';
  278.     prefix = NULL;
  279.     prefix_length = 0;
  280.     request = NULL;
  281.     request_length = 0;
  282.     condition = INCOMPLETE;                 // everthing's incomplete
  283. }
  284. /* The so-called "copy" constructor actually calls a copy() method after
  285.     doing some preliminary initialization.
  286. */
  287. File_Spec::File_Spec(const File_Spec& original)
  288. {
  289.     prefix = NULL;
  290.     prefix_length = 0;
  291.     request = NULL;
  292.     request_length = 0;
  293.     copy(original);
  294. }
  295. /* The destructor simply releases the memory, if any, assigned to prefix
  296.     and request.
  297. */
  298. File_Spec::.MDSD/w.MDNM/File_Spec(void)
  299. {
  300.     if (prefix != NULL)
  301.     {
  302.         delete[prefix_length] prefix;
  303.         prefix = NULL;
  304.         prefix_length = 0;
  305.     }
  306.     if (request != NULL)
  307.     {
  308.         delete[request_length] request;
  309.         request = NULL;
  310.         request_length = 0;
  311.     }
  312. }
  313. /* Tell 'em how we're doing */
  314. unsigned int    File_Spec::status(void)
  315. {
  316.     return(condition);
  317. }
  318. /* This method's purpose is to return a string containing a complete file
  319.     specification string for use by clients.
  320. */
  321. char    *File_Spec::filespec(void)
  322. {
  323.     int     length;
  324.     // first calculate the length of the file specification
  325.     length = strlen(device);
  326.     length += strlen(prefix);
  327.     length += strlen(name);
  328.     // + 2 to allow for the NUL-terminator and the colon
  329.     length += strlen(suffix) + 2;
  330.     // if request isn't already long enough then de-allocate the current
  331.     // pointer and allocate a new one
  332.     if (request_length < length)
  333.         if (!realloc(&request,&request_length,length))
  334.             return(NULL);
  335.     // build the string
  336.     strcpy(request,device);
  337.     if (prefix != NULL)
  338.         strcat(request,prefix);
  339.     strcat(request,name);
  340.     strcat(request,suffix);
  341.     return(request);
  342. }
  343. /*  get_device(), get_prefix(), get_name(), and get_suffix() are all essen-
  344.     tialy alike. if the request string isn't long enough then it is re-
  345.     allocated, then the attribute that was requested is copied into request.
  346. */
  347. char    *File_Spec::get_device(void)
  348. {
  349.     errno = 0;
  350.     if (request_length < SIZE_DEVICE + 1)
  351.         if (!realloc(&request,&request_length,SIZE_DEVICE + 1))
  352.             return(NULL);
  353.     strcpy(request,device);
  354.     return(request);
  355. }
  356. /*  Returning the prefix is a bit more complicated than the other get
  357.     routines.
  358. */
  359. char    *File_Spec::get_prefix(void)
  360. {
  361.     errno = 0;
  362.     if (prefix_length)
  363.     {
  364.         if (request_length < prefix_length)
  365.             if (!realloc(&request,&request_length,prefix_length))
  366.                 return(NULL);
  367.         strcpy(request,prefix);
  368.     }
  369.     else
  370.     // even if the prefix is NULL we promised to return something. Here,
  371.     // a string 1 character long consisting of a NUL-terminator
  372.     {
  373.         if (request_length == 0)
  374.             if (realloc(&request,&request_length,1))
  375.                 *request = '\0';
  376.     }
  377.     return(request);
  378. }
  379. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  380. char    *File_Spec::get_name(void)
  381. {
  382.     errno = 0;
  383.     if (request_length < SIZE_NAME + 1)
  384.         if (!realloc(&request,&request_length,SIZE_NAME + 1))
  385.             return(NULL);
  386.     strcpy(request,name);
  387.     return(request);
  388. }
  389. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  390. char    *File_Spec::get_suffix(void)
  391. {
  392.     errno = 0;
  393.     if (request_length < SIZE_SUFFIX + 1)
  394.         if (!realloc(&request,&request_length,SIZE_SUFFIX + 1))
  395.             return(NULL);
  396.     strcpy(request,suffix);
  397.     return(request);
  398. }
  399. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
  400.     as with the get_XXXX() methods above, change_device(), change_prefix(),
  401.     change_name(), and change_suffix() are basically the same. if the
  402.     current condition is "read-only" then return a FALSE. if a NULL string
  403.     is passed (note the default) the current object is truncated and the
  404.     corresponding incomplete flag is set. otherwise SIZE_n characters are
  405.     copied and the standard validity checks are made.
  406.  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  407. bool    File_Spec::change_device(const char *string)
  408. {
  409.     if (condition & READ_ONLY)
  410.         return(FALSE);
  411.     if (string == NULL || *string == '\0')
  412.         return(clear_attribute(device,FLAG_DEVICE));
  413.     strncpy(device,string,SIZE_DEVICE);
  414.     device[0] = toupper(device[0]);
  415.     device[1] = ':';
  416.     device[2] = '\0';
  417.     if (device[0] < 'A' || device[0] > 'Z')
  418.     {
  419.         condition |= FLAG_DEVICE << 4;
  420.         return(FALSE);
  421.     }
  422.     else
  423.         condition &= .MDSD/w.MDNM/(FLAG_DEVICE << 4);
  424.     condition &= .MDSD/w.MDNM/FLAG_DEVICE;
  425.     return(TRUE);
  426. }
  427. /*  get_prefix(), like change_prefix(), is somewhat more complicated than the
  428.     other change routines
  429. */
  430. bool    File_Spec::change_prefix(const char *string)
  431. {
  432.     int     new_length;
  433.     if (condition & READ_ONLY)
  434.         return(FALSE);
  435.     if (string == NULL || *string == '\0')
  436.         return(clear_attribute(prefix,FLAG_PREFIX));
  437.     errno = 0;
  438.     // get the size of the new prefix and if the existing prefix isn't long
  439.     // enough then re-allocate it
  440.     new_length = strlen(string);
  441.     if (new_length > SIZE_PREFIX)
  442.         new_length = SIZE_PREFIX;
  443.     if (prefix_length < new_length + 1)
  444.         if (!realloc(&prefix,&prefix_length,new_length + 1))
  445.             return(FALSE);
  446.     // copy in the new string and validate it.
  447.     strncpy(prefix,string,new_length);
  448.     prefix[new_length] = '\0';
  449.     if (check_chars(prefix,FLAG_PREFIX) == FALSE)
  450.         return(FALSE);
  451.     return(check_prefix());
  452. }
  453. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  454. bool    File_Spec::change_name(const char *string)
  455. {
  456.     int     i;
  457.     if (condition & READ_ONLY)
  458.         return(FALSE);
  459.     if (string == NULL || *string == '\0')
  460.         return(clear_attribute(name,FLAG_NAME));
  461.     i = 0;
  462.     while (string[i])
  463.     {
  464.         if (string[i] == '.')
  465.         {
  466.             change_suffix(&string[i + 1]);
  467.             if (i == 0)
  468.             {
  469.                 name[0] = '\0';
  470.                 condition |= FLAG_NAME;
  471.                 condition &= .MDSD/w.MDNM/(FLAG_NAME << 4);
  472.                 return(FALSE);
  473.             }
  474.             ++i;
  475.             break;
  476.         }
  477.         if (string[i] == ':' || string[i] == '\\')
  478.         {
  479.             condition |= FLAG_NAME << 4;
  480.             return(FALSE);
  481.         }
  482.         if (++i == SIZE_NAME)
  483.             break;
  484.     }
  485.     strncpy(name,string,i);
  486.     name[i] = '\0';
  487.     if (check_chars(name,FLAG_NAME) == FALSE)
  488.         return(FALSE);
  489.     i = strlen(name);
  490.     // as far as we're concerned name HAS to end with a dot.
  491.     if (name[i - 1] != '.')
  492.     {
  493.         if (i == SIZE_NAME)
  494.             i = SIZE_NAME - 1;
  495.         name[i++] = '.';
  496.         name[i] = '\0';
  497.     }
  498.     condition &= .MDSD/w.MDNM/FLAG_NAME;
  499.     return(TRUE);
  500. }
  501. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  502. bool    File_Spec::change_suffix(const char *string)
  503. {
  504.     if (condition & READ_ONLY)
  505.         return(FALSE);
  506.     if (string == NULL)
  507.     {
  508.         clear_attribute(suffix,FLAG_SUFFIX);
  509.         condition &= .MDSD/w.MDNM/FLAG_SUFFIX;     // unset the incomplete suffix flag
  510.     }
  511.     if (*string == '.')
  512.     {
  513.         ++string;
  514.         strncpy(suffix,string,SIZE_SUFFIX);
  515.     }
  516.     else
  517.         strncpy(suffix,string,SIZE_SUFFIX);
  518.     if (check_chars(suffix,FLAG_SUFFIX) == FALSE)
  519.         return(FALSE);
  520.     condition &= .MDSD/w.MDNM/FLAG_SUFFIX;
  521.     return(TRUE);
  522. }
  523. /*  This method determines whether or not the prefix is complete. */
  524. bool    File_Spec::check_prefix(void)
  525. {
  526.     int     i;
  527.  
  528.     // if the 1st character isn't a '\' then the prefix is relative to the
  529.     // current working directory.
  530.     if (prefix[0] != '\\')
  531.     {
  532.         condition |= FLAG_PREFIX;
  533.         return(FALSE);
  534.     }
  535.     i = 0;
  536.     // this loop checks for the presence of a dot followed by another dot
  537.     // or a dot followed by a backslash. either one indicates the prefix
  538.     // is relative the the current working directory.
  539.     while (prefix[i + 1])
  540.     {
  541.         if ((prefix[i] == '.') &&
  542.           (prefix[i + 1] == '\\' || prefix[i + 1] == '.'))
  543.             {
  544.                 condition |= FLAG_PREFIX;
  545.                 return(FALSE);
  546.             }
  547.         if (++i == SIZE_PREFIX - 1)
  548.             break;
  549.     }
  550.     // a prefix HAS to end with a '\'
  551.     if (prefix[i] != '\\')
  552.     {
  553.         prefix[i++] = '\\';
  554.         prefix[i] = '\0';
  555.     }
  556.     condition &= .MDSD/w.MDNM/FLAG_PREFIX;
  557.     return(TRUE);
  558. }
  559. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  560. void    File_Spec::read_only(bool flag)
  561. {
  562.     if (flag)
  563.         condition |= READ_ONLY;
  564.     else
  565.         condition &= .MDSD/w.MDNM/READ_ONLY;
  566. }
  567. /* This method actually attempts to complete a file specification. */
  568. bool    File_Spec::complete(void)
  569. {
  570.     char    *tstr;
  571.     char    drive;
  572.     if (condition & READ_ONLY)
  573.         return(FALSE);
  574.     // an invalid character in any of the components is an automatic failure
  575.     if (condition & INVALID_CHAR)
  576.         return(FALSE);
  577.     // no name is also an automatic failure
  578.     if (condition & FLAG_NAME)
  579.         return(FALSE);
  580.     // if no device specified then get the current drive
  581.     if (condition & FLAG_DEVICE)
  582.     {
  583.         drive = ll_get_drive();
  584.         device[0] = drive + 'A';
  585.         device[1] = ':';
  586.         device[2] = '\0';
  587.         condition &= .MDSD/w.MDNM/FLAG_DEVICE;
  588.     }
  589.     // if the prefix isn't complete call parse_prefix()
  590.     if (condition & FLAG_PREFIX)
  591.         if (parse_prefix() == FALSE)
  592.             return(FALSE);
  593.     // everything's alright so give the client the go-ahead
  594.     condition = 0;
  595.     return(TRUE);
  596. }
  597. /*  Looks rather unimpressive doesn't it. */
  598. File_Spec   File_Spec::operator=(const File_Spec& original)
  599. {
  600.     copy(original);
  601. }
  602. /*  As with the (char *string) constructor above, this routine uses a finite
  603.     state machine to produce a complete path. the existing prefix (if any)
  604.     is processed front to back while the current working directory (cwd) is
  605.     processed back to front. the end result is that the partial prefix is
  606.     appended at the correct point to the cwd.
  607. */
  608. bool    File_Spec::parse_prefix(void)
  609. {
  610.     int     prefix_elem;
  611.     int     cwd_elem;
  612.     int     state;
  613.     char    *cwd;
  614.     errno = 0;
  615.     // set the defaults
  616.     if ((cwd = new char[SIZE_PREFIX + 1]) == NULL)
  617.     {
  618.         errno = ENOMEM;
  619.         return(FALSE);
  620.     }
  621.     // since the directory returned by ll_get_cwd() doesn't begin with a
  622.     // '\' we'll start by adding one to the beginning of cwd
  623.     cwd[0] = '\\';
  624.     cwd[1] = '\0';
  625.     // get the current working directory for the specified drive
  626.     if (ll_get_cwd(device[0] - 64,&cwd[1]) == ERR)
  627.     {
  628.         delete[SIZE_PREFIX + 1] cwd;
  629.         return(FALSE);
  630.     }
  631.     // DOS doesn't append a '\' either so we will
  632.     cwd_elem = strlen(cwd);
  633.     if (cwd[1] != '\0')
  634.     {
  635.         cwd[cwd_elem] = '\\';
  636.         cwd[cwd_elem + 1] = '\0';
  637.     }
  638.     // if there was no prefix, there is now. assign it and return
  639.     if (prefix == NULL)
  640.     {
  641.         prefix_length = SIZE_PREFIX + 1;
  642.         prefix = cwd;
  643.         return(TRUE);
  644.     }
  645.     prefix_elem = 0;
  646.     state = 0;
  647.     do
  648.     {
  649.         switch(state)
  650.         {
  651.             case 0:
  652.                 if (prefix[prefix_elem] == '.')
  653.                     // a dot means check for another dot or '\'. goto state 1
  654.                     state = 1;
  655.                 else
  656.                     // DOS would give you a fit over this. since we're here,
  657.                     // check_prefix() found a relative component in the path.
  658.                     // however, the initial '\' means "start at the root".
  659.                     // so we go to the root by setting cwd_elem to zero and
  660.                     // start looking for a dot.
  661.                     if (prefix[prefix_elem] == '\\')
  662.                     {
  663.                         state = 1;
  664.                         cwd_elem = 0;
  665.                     }
  666.                     else
  667.                     {
  668.                       // our current character IS a character so get ready to
  669.                       // append it to cwd by going to state 3 and backing up
  670.                       // so we don't lose it.
  671.                       state = 3;
  672.                       --prefix_elem;
  673.                     }
  674.                 break;
  675.             case 1:                         // we have seen a dot (or a '\')
  676.                 if (prefix[prefix_elem] == '.')
  677.                     // another dot means go up a directory. enter state 2.
  678.                     state = 2;
  679.                 else
  680.                     // a '\' means stay here. get ready to append to the
  681.                     // cwd and enter state 3.
  682.                     if(prefix[prefix_elem] == '\\')
  683.                         state = 3;
  684.                     else
  685.                         // a character here means we just saw something like
  686.                         // .s - remain in the current directory and pass the
  687.                         // buck back to state 0.
  688.                         state = 0;
  689.                 break;
  690.             case 2:                         // two (or more) dots in a row
  691.                 if (prefix[prefix_elem] == '\\')
  692.                 {
  693.                     if (cwd_elem > 0)
  694.                         do
  695.                         {
  696.                             --cwd_elem;
  697.                         } while (cwd[cwd_elem] != '\\');
  698.                 }
  699.                 else
  700.                     // more than two dots in a row. maintain current state
  701.                     if (prefix[prefix_elem] == '.')
  702.                         break;
  703.                     else
  704.                         // this means we're seeing a "..s" type situation.
  705.                         // treat it as a "..\s" and back up one so we don't
  706.                         // lose the prefix character.
  707.                         --prefix_elem;
  708.                 state = 3;
  709.                 break;
  710.             case 3:                         // append the prefix to the cwd
  711.                 if (prefix[prefix_elem] == '.')
  712.                     // whoops, another dot. go back to state 1
  713.                     state = 1;
  714.                 else
  715.                     // more than one '\' in a row. don't change state.
  716.                     if (prefix[prefix_elem] == '\\')
  717.                         break;
  718.                     else
  719.                     {
  720.                         // the order of element increments is a bit peculiar
  721.                         // but remember we've been moving in opposite
  722.                         // directions
  723.                         do
  724.                         {
  725.                             ++cwd_elem;
  726.                             cwd[cwd_elem] = prefix[prefix_elem];
  727.                             ++prefix_elem;
  728.                         } while (prefix[prefix_elem] != '\\');
  729.                         ++cwd_elem;
  730.                         cwd[cwd_elem] = prefix[prefix_elem];
  731.                     }
  732.                 break;
  733.         };
  734.         ++prefix_elem;
  735.     } while (prefix[prefix_elem]);
  736.     cwd[++cwd_elem] = '\0';
  737.     // it worked! reset the prefix pointer and get out
  738.     delete[prefix_length] prefix;
  739.     prefix = cwd;
  740.     prefix_length = SIZE_PREFIX + 1;
  741.     return(TRUE);
  742. }
  743. /* This routine checks for valid DOS file name characters. */
  744. bool    File_Spec::check_chars(char *string, unsigned int attrib_flag)
  745. {
  746.     while (*string)
  747.     {
  748.         if ((*string < '!') ||
  749.             (*string == '"') ||
  750.             (*string > ')' && *string < '-') ||
  751.             (*string == '/') ||
  752.             (*string > '9' && *string < '@') ||
  753.             (*string == '[') ||
  754.             (*string > '\\' && *string < '^') ||
  755.             (*string == '|'))
  756.         {
  757.             condition |= attrib_flag << 4;
  758.             return(FALSE);
  759.         }
  760.         ++string;
  761.     }
  762.     condition &= .MDSD/w.MDNM/(attrib_flag << 4);
  763.     return(TRUE);
  764. }
  765. /* Since two methods (the 2nd constructor and the "=" operator) need to
  766.     make copies of other File_Spec instances, this private method is provided
  767.     to avoid duplicating the code. this method is also the main reason for
  768.     making File_Spec a friend of itself.
  769. */
  770. void    File_Spec::copy(const File_Spec& original)
  771. {
  772.     errno = 0;
  773.     strcpy(device,original.device);
  774.     if (prefix_length < original.prefix_length)
  775.         if (!realloc(&prefix,&prefix_length,original.prefix_length))
  776.             return;
  777.     strcpy(prefix,original.prefix);
  778.     strcpy(name,original.name);
  779.     strcpy(suffix,original.suffix);
  780.     condition = original.condition;
  781. }
  782. /*  clear_attribute sets the first element of the attribute it is passed to
  783.     a NUL and then resets the condition flags.
  784. */
  785. bool    File_Spec::clear_attribute(char *attribute, unsigned int attrib_flag)
  786. {
  787.     if (attribute != NULL)
  788.         *attribute = '\0';
  789.     condition |= attrib_flag;
  790.     condition &= .MDSD/w.MDNM/(attrib_flag << 4);
  791.     return(TRUE);
  792. }
  793. /* realloc() is used by the request and prefix attributes when they're changed
  794.     to see if they require re-allocating and to handle the re-allocation
  795.     if needed.
  796. */
  797. bool    File_Spec::realloc(char **pointer, int *length, int new_length)
  798. {
  799.     if (*length)
  800.         delete[*length] *pointer;
  801.     if ((*pointer = new char[new_length]) == NULL)
  802.     {
  803.         *length = 0;
  804.         *pointer = NULL;
  805.         return(FALSE);
  806.     }
  807.     *length = new_length;
  808.     return(TRUE);
  809. }
  810.  
  811.  
  812. LISTING THREE
  813.  
  814. /* FILE.HPP Written by Kevin D. Weeks Released to the Public Domain  */
  815.  
  816. #ifndef FILE_HPP                            // prevent multiple #includes
  817. #define FILE_HPP
  818.  
  819. #include "filespec.hpp"
  820.  
  821. // file access mode definitions
  822. #define F_RDONLY    0x0000
  823. #define F_WRONLY    0x0001
  824. #define F_RDWR      0x0002
  825. #define F_COMPAT    0x0000
  826. #define F_DENYALL   0x0010
  827. #define F_DENYWR    0x0020
  828. #define F_DENYRD    0x0030
  829. #define F_DENYNO    0x0040
  830.  
  831. // flag to determine whether reads and writes have a side-effect on the
  832. // file pointer
  833. #define F_ADVANCE   0x0100
  834.  
  835. class   File: public File_Spec
  836. {
  837.     // class attributes
  838.     int         handle;                     // DOS file handle
  839.     int         open_flags;                 // flags used to open or create
  840.     long        file_pos;                   // DOS file position
  841.     long        filelength;                 // length of file
  842.   // public class methods
  843.   public:
  844.     // constructors for file objects
  845.             File(void);
  846.             File(const File_Spec& original, bool open_flag = FALSE);
  847.             File(const char *name, bool open_flag = FALSE);
  848.     // destroy the object
  849.             .MDSD/w.MDNM/File(void);
  850.     bool    exists(void);                   // see if the file exists
  851.     bool    create(int mode_flags = 2, bool exclusive = TRUE);
  852.     bool    open(int mode_flags = 2);
  853.     bool    close(void);
  854.     unsigned int    read(void *buffer, unsigned int size);
  855.     // guarantees to write size bytes or fail
  856.     bool    write(const void *buffer, unsigned int size);
  857.     bool    truncate(void);              // truncate file at current position
  858.     // guarantees to position file pointer within file or fail
  859.     bool    set_position(long new_file_pos);
  860.     long    get_position(void);
  861.     long    size(void);
  862.     bool    rename(const char *newname);
  863.     bool    erase(void);
  864.     File    *copy(const char *newfile, bool overwrite = FALSE);
  865. };
  866. #endif
  867.  
  868.  
  869. LISTING FOUR
  870.  
  871. /* FILE.CPP Written by Kevin D. Weeks Released to the Public Domain  */
  872.  
  873. #include <errno.h>
  874. #include <io.h>
  875. #include <sys\stat.h>
  876. #include <dos.h>
  877. #include "file.hpp"
  878.  
  879. // this declaration instructs the compiler to NOT perform name-mangling
  880. // on these functions.
  881. extern "C"
  882. {
  883.     extern char     ll_get_drive(void);
  884.     extern int      ll_get_cwd(int, char *);
  885.     extern unsigned int ll_write(int, unsigned int, const void *);
  886. }
  887.  
  888. extern volatile int errno;
  889.  
  890. /*  An empty file object seems silly but here it is anyway */
  891. File::File(void)
  892. {
  893.     handle = -1;
  894.     file_pos = 0L;
  895.     open_flags = 0;
  896.     filelength = 0L;
  897. }
  898.  
  899. /*  This is the File version of the "copy" constructor. it is posible to
  900.     open the file when the object is instantiated by passing TRUE as a
  901.     second parameter.
  902. */
  903. File::File(const File_Spec& original, bool open_file):(original)
  904. {
  905.     handle = -1;
  906.     file_pos = 0L;
  907.     open_flags = 0;
  908.     if ((filelength = filesize(filespec())) == -1L)
  909.         filelength = 0L;
  910.     if (open_file == TRUE)
  911.         open();
  912. }
  913.  
  914. /*  This constructor is the same as the one above. The differences in pass
  915.     parameters are handled by their respective ancestors.
  916. */
  917. File::File(const char *name, bool open_file):(name)
  918. {
  919.     handle = -1;
  920.     file_pos = 0L;
  921.     open_flags = 0;
  922.     if ((filelength = filesize(filespec())) == -1L)
  923.         filelength = 0L;
  924.     if (open_file == TRUE)
  925.         open();
  926. }
  927.  
  928. /*  File destructor */
  929. File::.MDSD/.MDSD/w.MDNM/.MDNM/File(void)
  930. {
  931.     if (handle > 0)
  932.         close();
  933. }
  934.  
  935. /*  Does the file exist? */
  936. bool    File::exists(void)
  937. {
  938.     if (!complete())                      // check for a completed file spec
  939.         return(FALSE);                    // and either fail if not
  940.     if (findfirst(filespec(),0) == NULL)  // or else check for directory entry
  941.         return(FALSE);
  942.     return(TRUE);
  943. }
  944.  
  945. /* Create the file. */
  946. bool    File::create(int mode_flags, bool exclusive)
  947. {
  948.     int     tmp_handle;
  949.     if (!complete())                        // is the file spec complete?
  950.         return(FALSE);
  951.     if (handle > -1)                        // if the file is open
  952.     {
  953.         if (exclusive)
  954.             return(FALSE);
  955.         else
  956.             close();
  957.     }
  958.     else
  959.         if (exists())                       // if the file exists
  960.             if (exclusive)                  // if this flag is TRUE
  961.             {                               // return an error
  962.                 errno = EEXIST;
  963.                 return(FALSE);
  964.             }
  965.     // create the the file and then close it to re-open with the appropriate
  966.     // mode flags set
  967.     if ((tmp_handle = creat(filespec(),S_IWRITE | S_IREAD)) == ERR)
  968.         return(FALSE);
  969.     ::close(tmp_handle);                  // the :: means use the library close
  970.     if (open(mode_flags) == FALSE)          // no :: - use the File method
  971.         return(FALSE);
  972.     set_position(0L);                       // position at the beginning
  973.     filelength = 0L;
  974.     read_only(TRUE);                        // tell File_Spec that it can't
  975.                                             // be changed
  976.     return(TRUE);
  977. }
  978.  
  979. /* Open the file. */
  980. bool    File::open(int mode_flags)
  981. {
  982.     if (handle > -1)                        // if the file is already open
  983.          return(TRUE);                      //      don't re-open it
  984.     if (!complete())                        // check for a complete file spec
  985.         return(FALSE);
  986.     // use the standard library to actually open it ( ::open(...) )
  987.     if ((handle = ::open(filespec(),mode_flags)) == ERR)
  988.         return(FALSE);
  989.     open_flags = mode_flags;                // keep the mode flags
  990.     read_only(TRUE);                        // tell File_Spec not to change a
  991.                                             //      thing
  992.     return(TRUE);
  993. }
  994.  
  995. /* Close the file. */
  996. bool    File::close(void)
  997. {
  998.     if (handle > -1)                        // if the file's open
  999.         if (::close(handle) == ERR)         //     close it
  1000.             return(FALSE);
  1001.     handle = -1;                            // and re-initialize everything
  1002.     file_pos = 0L;
  1003.     open_flags = 0;
  1004.     read_only(FALSE);                       // File_Spec can change again
  1005. }
  1006.  
  1007. /*  Read the file. NOTE: bytes actually read may be less than requested.  */
  1008. unsigned int    File::read(void *buffer, unsigned int num_bytes)
  1009. {
  1010.     int     bytes_read;
  1011.     if (handle < 0)                         // make sure the file's open
  1012.     {
  1013.         errno = EBADF;
  1014.         return(FALSE);
  1015.     }
  1016.     // first set the file position. if auto-advance is on set_position()
  1017.     // will just return. otherwise it will move the file pointer to where
  1018.     // it should be. then use the standard read to read the file
  1019.     if (set_position(file_pos) != FALSE)
  1020.         if ((bytes_read = ::read(handle,buffer,num_bytes)) == ERR)
  1021.             return(FALSE);
  1022.     // if auto-advance is on we still need to keep ourselves current
  1023.     if (open_flags & F_ADVANCE)
  1024.         file_pos += (long)bytes_read;
  1025.     // return the number of bytes actually read
  1026.     return(bytes_read);
  1027. }
  1028.  
  1029. /*  Write to the file. In this case failure to write the number of bytes
  1030.     specified IS considered a failure.
  1031. */
  1032. bool    File::write(const void *buffer, unsigned int num_bytes)
  1033. {
  1034.     if (handle < 0)                        // is the file open?
  1035.     {
  1036.         errno = EBADF;
  1037.         return(FALSE);
  1038.     }
  1039.     if (num_bytes == 0)                  // if zero bytes are to be written
  1040.         return(TRUE);                    // return WITHOUT truncating the file
  1041.     // make sure the file pointer is positioned right and then call our
  1042.     // low level write routine to write it. there's no reason to clutter up
  1043.     // the program with the library write()
  1044.     if (set_position(file_pos) != FALSE)
  1045.         if (ll_write(handle,num_bytes,buffer) < num_bytes)
  1046.         {
  1047.             // at this point we failed to write as many bytes as desired. to
  1048.             // eliminate side effects we truncate
  1049.             truncate();
  1050.             return(FALSE);
  1051.         }
  1052.     // if we wrote at the end of the file, increase its length
  1053.     if (file_pos == filelength)
  1054.         filelength += (unsigned long)num_bytes;
  1055.     if (open_flags & F_ADVANCE)             // check for auto-advance
  1056.         file_pos += (long)num_bytes;
  1057.     return(TRUE);
  1058. }
  1059.  
  1060. /*  Truncate chops a file off at the current file_position. */
  1061. bool    File::truncate(void)
  1062. {
  1063.     if (handle < 0)                         // don't bother if we're not open
  1064.     {
  1065.         errno = EBADF;
  1066.         return(FALSE);
  1067.     }
  1068.     // re-set the file pointer and write zero bytes
  1069.     if (set_position(file_pos) != ERR)
  1070.         if (!ll_write(handle,0,NULL))
  1071.             return(FALSE);
  1072.     filelength = file_pos;                  // re-set the length
  1073.     return(TRUE);
  1074. }
  1075.  
  1076. /*  Position the DOS file pointer */
  1077. bool    File::set_position(long new_file_pos)
  1078. {
  1079.     if (handle < 0)                         // guess!
  1080.     {
  1081.         errno = EBADF;
  1082.         return(FALSE);
  1083.     }
  1084.     // first make sure we're not attempting to set before the beginning or
  1085.     // after the end of the file.
  1086.     if (new_file_pos > filelength || new_file_pos < 0L)
  1087.         return(FALSE);
  1088.     // position it
  1089.     if (lseek(handle,new_file_pos,SEEK_SET) == -1L)
  1090.         return(FALSE);
  1091.     file_pos = new_file_pos;
  1092.     return(TRUE);
  1093. }
  1094.  
  1095. /*  Get the current file position */
  1096. long    File::get_position(void)
  1097. {
  1098.     return(file_pos);
  1099. }
  1100.  
  1101. /*  Get the file size */
  1102. long    File::size(void)
  1103. {
  1104.     long    length;
  1105.     if (handle > -1)
  1106.         return(filelength);
  1107.     else
  1108.     {
  1109.         if (open() == FALSE)
  1110.             return(0L);
  1111.         length = filelength;
  1112.         close();
  1113.         return(length);
  1114.     }
  1115. }
  1116.  
  1117. /*  If we attempt to rename an open file it is first closed and then
  1118.     reopened after the rename.
  1119.  */
  1120. bool    File::rename(const char *newname)
  1121. {
  1122.     bool    reopen = FALSE;
  1123.     int     tmp_flags;
  1124.     int     i;
  1125.     if (handle > -1)                        // close the file if it's open
  1126.     {
  1127.         tmp_flags = open_flags;
  1128.         close();
  1129.         reopen = TRUE;
  1130.     }
  1131.     else
  1132.         if (exists() == FALSE)              // make sure the file exists
  1133.             return(FALSE);
  1134.     // create a new file spec just like this one (note that it's also
  1135.     // instantiated at this point)
  1136.     File_Spec newspec = *this;
  1137.     newspec.change_name(newname);           // and then change the name
  1138.     if (::rename(filespec(),newspec.filespec()) != 0)
  1139.     {
  1140.         if (reopen)
  1141.             // pass the existing open_flags in case the default wasn't used
  1142.             // when the file was originally opened.
  1143.             open(tmp_flags);
  1144.         return(FALSE);
  1145.     }
  1146.     change_name(newname);                   // now update this file name
  1147.     if (reopen)
  1148.         return(open(tmp_flags));
  1149.     return(TRUE);
  1150. }
  1151.  
  1152. /*  Erase the file. if it's open, close it first. */
  1153. bool    File::erase(void)
  1154. {
  1155.     if (handle > -1)
  1156.         close();
  1157.     if (unlink(filespec()) == ERR)
  1158.         return(FALSE);
  1159.     return(TRUE);
  1160. }
  1161.  
  1162. /*  This might also be a good opportunity for operator overloading.  */
  1163. File    *File::copy(const char *newname, bool overwrite)
  1164. {
  1165.     File            *newfile;               // file to copy to
  1166.     char            *buffer;                // I/O buffer
  1167.     unsigned int    buf_size;               // size of I/O buffer
  1168.     unsigned int    num_bytes;              // number of bytes transfered
  1169.     long            tmp_file_pos;           // temporary file position holder
  1170.     bool            re_close = FALSE;       // flag indicating if source file
  1171.                                             // should be closed following the
  1172.                                             // copy (to avoid side effects)
  1173.     int             tmp_old_flags;          // the original source open flags
  1174.  
  1175.     errno = 0;
  1176.     // first create the new object instance
  1177.     if ((newfile = new File(newname)) == NULL)
  1178.     {
  1179.         errno = ENOMEM;
  1180.         return(NULL);
  1181.     }
  1182.     // then create the new file (invert overwrite for create)
  1183.     if (!(newfile->create(F_ADVANCE | F_RDWR,(bool)!overwrite)))
  1184.     {
  1185.         delete newfile;
  1186.         return(NULL);
  1187.     }
  1188.     // attempt to allocate a buffer. loop until successful or fail at 1 char
  1189.     buf_size = 32768;
  1190.     while ((buffer = new char[buf_size]) == NULL)
  1191.     {
  1192.         buf_size /= 2;
  1193.         if (buf_size == 1)
  1194.         {
  1195.             errno = ENOMEM;
  1196.             delete newfile;
  1197.             return(NULL);
  1198.         }
  1199.     }
  1200.     if (handle < 0)                         // if the source file isn't open
  1201.     {
  1202.         if (!open())                        // open it
  1203.         {
  1204.             newfile->close();               // if we can't open the source
  1205.             newfile->erase();               // file we need to clean up
  1206.             delete newfile;
  1207.             delete[buf_size] buffer;
  1208.             return(NULL);
  1209.         }
  1210.         re_close = TRUE;                    // copy() opened it so copy()
  1211.     }                                       // should close it
  1212.     tmp_old_flags = open_flags;             // keep the original open flags
  1213.     open_flags |= F_ADVANCE;                // and turn auto-advance on
  1214.     tmp_file_pos = file_pos;                // keep the original file pointer
  1215.     set_position(0L);                       // go to the beginning of the file
  1216.     // loop until the entire file has been copied
  1217.     while (num_bytes = read(buffer,buf_size))
  1218.     {
  1219.         if (newfile->write(buffer,num_bytes) == FALSE)
  1220.         {                                   // if a write error occurs we
  1221.             newfile->close();               // need to clean up the mess
  1222.             newfile->erase();               // and return an error
  1223.             delete newfile;
  1224.             delete[buf_size] buffer;
  1225.             open_flags = tmp_old_flags;
  1226.             set_position(tmp_file_pos);
  1227.             if (re_close)
  1228.                 close();
  1229.             return(NULL);
  1230.         }
  1231.     }
  1232.     // clean up and return the new file
  1233.     newfile->close();
  1234.     delete[buf_size] buffer;
  1235.     open_flags = tmp_old_flags;
  1236.     set_position(tmp_file_pos);
  1237.     if (re_close)
  1238.         close();
  1239.     return(newfile);
  1240. }
  1241.  
  1242.  
  1243. LISTING FIVE
  1244.  
  1245. ;******************************************************************************
  1246. ;   LOWIO.ASM Written by Kevin D. Weeks Released to the Public Domain
  1247. ;
  1248.  
  1249. include MACROS.ASM                          ; macro file provided by Zortech
  1250.  
  1251. ; import errno
  1252. begdata
  1253.     extrn  _errno:word
  1254. enddata
  1255.  
  1256. ;* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1257. ;   bool    ll_write(int file_handle, unsigned int num_bytes, void *buffer);
  1258. ;   ll_write simply makes a call to DOS for a write. it varies in two ways
  1259. ;   from the standard C write().
  1260. ;   1. the order of pass parameters (to simplify dealing with 80x86 segments)
  1261. ;   2. it WILL truncate a file
  1262. ;
  1263. begcode ll_write
  1264.     c_public ll_write
  1265. func ll_write
  1266.     push    bp
  1267.     mov     bp,sp
  1268.     push    bx
  1269.     push    cx
  1270.     push    dx
  1271.     push    ds
  1272.  
  1273.     mov     bx,P[bp]                        ; get file handle from stack
  1274.     mov     cx,P[bp + 2]                    ; get number of bytes to write
  1275.     mov     dx,P[bp + 4]                    ; get offset of buffer
  1276. if LPTR                                     ; if large memory model
  1277.     mov     ds,P[bp + 6]                    ; get segment of buffer
  1278. endif
  1279.     mov     ax,4000h                        ; dos write file function
  1280.     int     21h                             ; call dos
  1281.     jc      write_err                       ; carry flag indicates error
  1282.     jmp     write_ret
  1283. write_err:
  1284.     mov     _errno,ax                       ; set errno to error
  1285. write_ret:
  1286.     pop     ds
  1287.     pop     dx
  1288.     pop     cx
  1289.     pop     bx
  1290.     mov     sp,bp
  1291.     pop     bp
  1292.     ret
  1293. c_endp  ll_write
  1294. endcode ll_write
  1295.  
  1296. ;* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1297. ;   int     ll_get_drive(void);
  1298. ;   ll_get_drive simply returns the current looged disk drive. there is no
  1299. ;   error return.
  1300. ;
  1301. begcode ll_get_drive
  1302.     c_public ll_get_drive
  1303. func ll_get_drive
  1304.     push    bp
  1305.     mov     bp,sp
  1306.     mov     ax,1900h                        ; dos get current drive function
  1307.     int     21h                             ; call dos
  1308.     xor     ah,ah                           ; clear high byte
  1309.     mov     sp,bp
  1310.     pop     bp
  1311.     ret
  1312. c_endp  ll_get_drive
  1313. endcode ll_get_drive
  1314.  
  1315. ;* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1316. ;   bool    ll_get_cwd(int drive);
  1317. ;   ll_get_cwd gets the current working directory for the specified drive.
  1318. ;   0 means the current drive, 1 means drive A, 2 means drive B, etc. it
  1319. ;   returns a 0 if an error occurs and errno is set.
  1320. ;
  1321. begcode ll_get_cwd
  1322.     c_public ll_get_cwd
  1323. func ll_get_cwd
  1324.     push    bp
  1325.     mov     bp,sp
  1326.     push    dx
  1327.     push    si
  1328.     push    ds
  1329.     mov     dx,P[bp]                        ; get drive
  1330.     mov     si,P[bp + 2]                    ; get offset of buffer
  1331. if LPTR                                     ; if large memory model
  1332.     mov     ds,P[bp + 4]                    ; get segment of buffer
  1333. endif
  1334.     mov     ax,4700h                        ; dos get current cwd function
  1335.     int     21h                             ; call DOS
  1336.     jc      get_cwd_err                     ; carry flag indicates error
  1337.     xor     ax,ax
  1338.     jmp     get_cwd_ret
  1339. get_cwd_err:
  1340.     mov     _errno,ax                       ; set errno to error
  1341.     mov     ax,0ffffh                       ; & set ax to -1
  1342. get_cwd_ret:
  1343.     pop     ds
  1344.     pop     si
  1345.     pop     dx
  1346.     mov     sp,bp
  1347.     pop     bp
  1348.     ret
  1349. c_endp  ll_get_cwd
  1350. endcode ll_get_cwd
  1351.  
  1352. END
  1353.  
  1354.  
  1355. LISTING SIX
  1356.  
  1357. /* FILETEST.CPP Written by Kevin D. Weeks Released to the Public Domain  */
  1358.  
  1359. #include <stdio.h>
  1360. #include <errno.h>
  1361. #include <string.h>
  1362. #include "file.hpp"
  1363.  
  1364. // File_Spec test cases
  1365. char    *device_test[] =
  1366. {
  1367.     "",                                     // no device
  1368.     "a:",                                   // complete device
  1369.     ">:",                                   // invalid file char
  1370.     "ab:",                                  // device too long
  1371.     "a::",                                  // invalid char (double colon)
  1372.     NULL
  1373. };
  1374.  
  1375. char    *prefix_test[] =
  1376. {
  1377.   "",                                     // no prefix
  1378.   "\\sub\\",                              // complete prefix
  1379.   "\\>sub\\",                             // complete with invalid file char
  1380.   // prefix too long
  1381.  
  1382. "\\0123456789\\0123456789\\0123456789\\0123456789\\0123456789\\0123456789\\",
  1383.   "sub.dir\\",                            // incomplete prefix (no backslash)
  1384.   "\\\\sub\\",                            // double back slash
  1385.   "\\sub.dir\\",                          // complete prefix with extension
  1386.   "sub1\\sub2\\",                         // bi-level incomplete prefix
  1387.   "\\sub1\\sub2\\",                       // bi-level conplete prefix
  1388.   "..\\sub",                              // relative, incomplete prefix
  1389.   "..\\sub\\",                            //     "   ,      "       "
  1390.   "..\\",                                 //     "   ,      "       "
  1391.   ".\\sub\\",                             //     "   ,      "       "
  1392.   "\\..\\sub\\",                          // relative but starts at root
  1393.   "..\\>sub\\",                           // relative with invalid file char
  1394.   "\\",                                   // complete prefix
  1395.   NULL
  1396. };
  1397.  
  1398. char    *name_test[] =
  1399. {
  1400.     "",                                     // no name
  1401.     "file.",                                 // complete name
  1402.     "file>.",                               // complete with invalid file char
  1403.     "filetest1.",                           // name too long
  1404.     "file..",                               // invalid char (double dot)
  1405.     "file",                                // incomplete, no dot
  1406.     NULL
  1407. };
  1408.  
  1409. char    *suffix_test[] =
  1410. {
  1411.     "",                                     // no suffix
  1412.     ".tst",                                  // complete suffix
  1413.     ".ts>",                                  // complete with invalid file
  1414. char
  1415.     ".tst1",                                 // suffix too long
  1416.     NULL
  1417. };
  1418.  
  1419. char    **test[] =
  1420. {
  1421.     device_test,
  1422.     prefix_test,
  1423.     name_test,
  1424.     suffix_test
  1425. };
  1426.  
  1427. char    *test_type[] =
  1428. {
  1429.     "DEVICE ",
  1430.     "PREFIX ",
  1431.     "NAME ",
  1432.     "SUFFIX "
  1433. };
  1434.  
  1435. extern volatile int  errno;
  1436. void    check_file_spec(void);
  1437. void    check_file(void);
  1438. void    make_filespec(char *test_case);
  1439. void    print_condition(File_Spec& file,char *test_name,char *test_case);
  1440.  
  1441. int     main(void)
  1442. {
  1443.     check_file_spec();
  1444.     check_file();
  1445. }
  1446.  
  1447. void    check_file_spec()
  1448. {
  1449.     int     i, j, k;
  1450.     char    test_case[81];
  1451.     char    title[81];
  1452.  
  1453.     printf("\nTESTING File_Spec...\n\n");
  1454.     File_Spec   file1;                      // check void constructor
  1455.     print_condition(file1,"void constructor"," ");
  1456.  
  1457.     for (i = 0; i < 4; i++)
  1458.     {
  1459.         printf("CHECKING %s\n",test_type[i]);
  1460.         j = 0;
  1461.         while (test[i][j] != NULL)
  1462.         {
  1463.             make_filespec(test[i][j]);
  1464.             ++j;
  1465.         }
  1466.     }
  1467.  
  1468.     // check first four complete combinations
  1469.     printf("\nCHECKING COMPLETE FILE SPECS\n");
  1470.     for (i = 0; i < 4; i++)
  1471.     {
  1472.         strcpy(test_case,test[0][i]);
  1473.         strcat(test_case,test[1][i]);
  1474.         strcat(test_case,test[2][i]);
  1475.         strcat(test_case,&test[3][i][1]);
  1476.         make_filespec(test_case);
  1477.     }
  1478.  
  1479.     for (i = 0; i < 4; i++)
  1480.     {
  1481.         printf("CHECKING change_%s\n",test_type[i]);
  1482.         j = 0;
  1483.         while (test[i][j] != NULL)
  1484.         {
  1485.             switch (i)
  1486.             {
  1487.                 case 0:
  1488.                     if (file1.change_device(test[i][j]) == FALSE)
  1489.                         printf("Error changing device");
  1490.                     break;
  1491.                 case 1:
  1492.                     if (file1.change_prefix(test[i][j]) == FALSE)
  1493.                         printf("Error changing prefix");
  1494.                     break;
  1495.                 case 2:
  1496.                     if (file1.change_name(test[i][j]) == FALSE)
  1497.                         printf("Error changing name");
  1498.                     break;
  1499.                 case 3:
  1500.                     if (file1.change_suffix(test[i][j]) == FALSE)
  1501.                         printf("Error changing suffix");
  1502.                     break;
  1503.             };
  1504.             print_condition(file1," ",test[i][j]);
  1505.             ++j;
  1506.         }
  1507.     }
  1508.     printf("\nCOMPLETION TEST\n");
  1509.     file1.change_device();                  // erase current device
  1510.     file1.change_prefix();                  // & prefix
  1511.     print_condition(file1,"BEFORE"," ");
  1512.     file1.complete();
  1513.     print_condition(file1,"AFTER"," ");
  1514.  
  1515.     File_Spec file2 = file1;
  1516.     print_condition(file2,"\nTEST '=' OPERATOR\n","file2 = file1");
  1517.  
  1518. }
  1519.  
  1520. void    make_filespec(char *test_case)
  1521. {
  1522.     File_Spec file2(test_case);
  1523.     print_condition(file2,"char constructor",test_case);
  1524.     File_Spec file3(file2);
  1525.     print_condition(file3,"copy constructor",test_case);
  1526. }
  1527.  
  1528. void    print_condition(File_Spec& file, char *test_name, char *test_case)
  1529. {
  1530.     unsigned int    status;
  1531.     char            *completion;
  1532.     char            *character;
  1533.     static char     incomplete[] = {"INCOMPLETE"};
  1534.     static char     complete[] = {"  complete"};
  1535.     static char     invalid[] = {"INVALID CHAR"};
  1536.     static char     valid[] = {"  chars ok  "};
  1537.  
  1538.     printf("%s\t%s\n",test_name,test_case);
  1539.     status = file.status();
  1540.     printf("file condition: %x\n",status);
  1541.  
  1542.     completion = (status & FLAG_DEVICE) ? incomplete : complete;
  1543.     character = (status & (FLAG_DEVICE << 4)) ? invalid : valid;
  1544.     printf("\tdevice: %-9s\t%s\t%s\n",file.get_device(),completion,character);
  1545.  
  1546.     completion = (status & FLAG_PREFIX) ? incomplete : complete;
  1547.     character = (status & (FLAG_PREFIX << 4)) ? invalid : valid;
  1548.     printf("\tprefix: %-9s\t%s\t%s\n",file.get_prefix(),completion,character);
  1549.  
  1550.     completion = (status & FLAG_NAME) ? incomplete : complete;
  1551.     character = (status & (FLAG_NAME << 4)) ? invalid : valid;
  1552.     printf("\t  name: %-9s\t%s\t%s\n",file.get_name(),completion,character);
  1553.  
  1554.     character = (status & (FLAG_SUFFIX << 4)) ? invalid : valid;
  1555.     printf("\tsuffix: %-9s\t%s\t%s\n",file.get_suffix()," ",character);
  1556.  
  1557.     printf("\tfilespec: %s\n\n",file.filespec());
  1558. }
  1559.  
  1560. void    check_file(void)
  1561. {
  1562.     char    buffer[81];
  1563.     int     i;
  1564.  
  1565.     printf("\n\n\nTESTING File...\n\n\n");
  1566.  
  1567.     /* we won't try to perform any constructor tests since most of the
  1568.         attributes are in-accessable and therefore best checked using
  1569.         either a source-level debugger or printf statements. */
  1570.     File file1("file.tst");
  1571.  
  1572.     printf("Creating %s\n",file1.filespec());
  1573.     if (file1.create() == FALSE)
  1574.     {
  1575.         printf("%s already exists. Re-creating it.\n",file1.filespec());
  1576.         if (file1.create(F_RDWR,FALSE) == FALSE)
  1577.         {
  1578.             printf("Error re-creating %s\n",file1.filespec());
  1579.             perror("");
  1580.             return;
  1581.         }
  1582.     }
  1583.     printf("File %s successfully created and opened\n",file1.filespec());
  1584.  
  1585.     strcpy(buffer,"this is a test file");
  1586.     i = strlen(buffer);
  1587.     if (file1.write(buffer,i) == FALSE)
  1588.     {
  1589.         perror("Error writing");
  1590.         printf("Closing file\n");
  1591.         return;
  1592.     }
  1593.     printf("\"%s\" written to file\n",buffer);
  1594.     printf("Current file position is: %ld\n",file1.get_position());
  1595.     printf("Current file length is: %ld\n",file1.size());
  1596.     if (file1.read(buffer,i) == FALSE)
  1597.     {
  1598.         perror("Error reading");
  1599.         printf("Closing file\n");
  1600.         return;
  1601.     }
  1602.     printf("\"%s\" read from file\n",buffer);
  1603.     if (file1.rename("test.fil") == FALSE)
  1604.     {
  1605.         perror("Error renaming");
  1606.         printf("Closing file\n");
  1607.         return;
  1608.     }
  1609.     printf("File renamed to %s\n",file1.filespec());
  1610.  
  1611.     File *file2 = file1.copy("test2.fil");
  1612.     if (errno)
  1613.     {
  1614.         perror("test2.fil");
  1615.         printf("Overwriting it.\n");
  1616.         file2 = file1.copy("test2.fil",TRUE);
  1617.   }
  1618.   if (file2->exists())
  1619.    printf("%s successfully copied to %s\n",file1.filespec(),file2-
  1620. >filespec());
  1621.   else
  1622.   {
  1623.         printf("Copy failed.\n");
  1624.         return;
  1625.     }
  1626.  
  1627.     delete file2;
  1628.  
  1629. }